/*
 * CrashDump.cpp
 *
 *  Created on: 2021年6月11日
 *      Author: chuanjiang.zh
 */

#include "CrashDump.h"

#include <windows.h>
#include <imagehlp.h>
#include <stdlib.h>
#include <tchar.h>
#include <stdlib.h>
#include <stdio.h>

#pragma comment(lib, "dbghelp.lib")



static int s_dumpCount = 5;
static TCHAR s_reportPath[MAX_PATH] = {0};


void WinTrace(LPCTSTR fmt, ...)
{
	va_list ap;
	va_start(ap, fmt);

	TCHAR buffer[1024];
#if defined(_MSC_VER) && (_MSC_VER >= 1400)
	int len = vsprintf_s(buffer, 1024, fmt, ap);
#else
	int len = vsprintf(buffer, fmt, ap);
#endif //
	if (len >= 0)
	{
		buffer[len] = '\0';

		OutputDebugString(buffer);
	}

	va_end(ap);
}


class AutoFile
{
public:
    explicit AutoFile(const char* path):
        m_handle(INVALID_HANDLE_VALUE)
    {
        m_handle = CreateFile(path, GENERIC_READ,
                        FILE_SHARE_READ | FILE_SHARE_WRITE,
                        NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL);
    }

    explicit AutoFile(HANDLE handle):
            m_handle(handle)
    {
    }

    ~AutoFile()
    {
        if (m_handle != INVALID_HANDLE_VALUE)
        {
            ::CloseHandle(m_handle);
        }
    }

    bool operator ! () const
    {
        return (m_handle == INVALID_HANDLE_VALUE);
    }

    operator bool () const
    {
        return (m_handle != INVALID_HANDLE_VALUE);
    }

    HANDLE  m_handle;

};



static bool existFile(LPCTSTR path)
{
    return (GetFileAttributes(path) != 0xFFFFFFFF);
}

static time_t filetime_to_time(const FILETIME& ft)
{
    ULARGE_INTEGER ull;
    ull.LowPart = ft.dwLowDateTime;
    ull.HighPart = ft.dwHighDateTime;
    return ull.QuadPart / 10000000ULL - 11644473600ULL;
}

static time_t getctime(LPCTSTR filepath)
{
    AutoFile file(filepath);
    FILETIME ft;
    memset(&ft, 0, sizeof(ft));
    GetFileTime(file.m_handle, &ft, NULL, NULL);
    return filetime_to_time(ft);
}

static time_t getmtime(LPCTSTR filepath)
{
	AutoFile file(filepath);
	FILETIME ft;
	memset(&ft, 0, sizeof(ft));
	GetFileTime(file.m_handle, NULL, NULL, &ft);
	if ((ft.dwHighDateTime == 0) && (ft.dwLowDateTime == 0))
	{
		return 0;
	}
	return filetime_to_time(ft);
}


static bool execute(LPTSTR filepath)
{
    STARTUPINFO si;
    ZeroMemory(&si, sizeof(si));
    si.cb = sizeof(si);
    si.dwFlags = STARTF_USESHOWWINDOW;
    si.wShowWindow = SW_HIDE;

    PROCESS_INFORMATION pi;
    ZeroMemory(&pi, sizeof(pi));

    if (CreateProcess(
        NULL,                   // name of executable module
        filepath,          // command line string
        NULL,                   // process attributes
        NULL,                   // thread attributes
        FALSE,                  // handle inheritance option
        0,                      // creation flags
        NULL,                   // new environment block
        NULL,                   // current directory name
        &si,                    // startup information
        &pi))                   // process information
    {
        // XCrashReport.exe was successfully started, so
        // suppress the standard crash dialog

        CloseHandle( pi.hProcess );
        CloseHandle( pi.hThread );
        return true;
    }
    else
    {
        // XCrashReport.exe was not started - let
        // the standard crash dialog appear
        return false;
    }
}



inline BOOL IsDataSectionNeeded(const WCHAR* pModuleName)
{
    if(pModuleName == 0)
    {
        return FALSE;
    }

    WCHAR szFileName[_MAX_FNAME] = L"";
    _wsplitpath(pModuleName, NULL, NULL, szFileName, NULL);
    if (wcsicmp(szFileName, L"ntdll") == 0)
    {
        return TRUE;
    }
    return FALSE;
}

inline BOOL CALLBACK MiniDumpCallback(PVOID  pParam,
    const PMINIDUMP_CALLBACK_INPUT   pInput,
    PMINIDUMP_CALLBACK_OUTPUT        pOutput)
{
    if(pInput == 0 || pOutput == 0)
        return FALSE;

    switch(pInput->CallbackType)
    {
    case ModuleCallback:
        if (pOutput->ModuleWriteFlags & ModuleWriteDataSeg)
        {
            if (!IsDataSectionNeeded(pInput->Module.FullPath))
            {
                pOutput->ModuleWriteFlags &= (~ModuleWriteDataSeg);
            }
        }
        return TRUE;
    case IncludeModuleCallback:
    case IncludeThreadCallback:
    case ThreadCallback:
    case ThreadExCallback:
        return TRUE;

    default:;
    }

    return FALSE;
}

//创建Dump文件
inline void CreateMiniDump(EXCEPTION_POINTERS* pep, LPCTSTR strFileName)
{
    HANDLE hFile = CreateFile(strFileName, GENERIC_WRITE, 0, NULL, CREATE_ALWAYS, FILE_ATTRIBUTE_NORMAL, NULL);
    if((hFile != NULL) && (hFile != INVALID_HANDLE_VALUE))
    {
        MINIDUMP_EXCEPTION_INFORMATION mdei;
        mdei.ThreadId           = GetCurrentThreadId();
        mdei.ExceptionPointers  = pep;
        mdei.ClientPointers     = FALSE;
        MINIDUMP_CALLBACK_INFORMATION mci;
        mci.CallbackRoutine     = (MINIDUMP_CALLBACK_ROUTINE)MiniDumpCallback;
        mci.CallbackParam       = 0;
        MINIDUMP_TYPE mdt       = (MINIDUMP_TYPE)0x0000ffff;
        MiniDumpWriteDump(GetCurrentProcess(), GetCurrentProcessId(), hFile, MiniDumpNormal, &mdei, NULL, &mci);

        CloseHandle(hFile);
    }
}

LPTOP_LEVEL_EXCEPTION_FILTER WINAPI MyDummySetUnhandledExceptionFilter(LPTOP_LEVEL_EXCEPTION_FILTER lpTopLevelExceptionFilter)
{
    return NULL;
}

BOOL PreventSetUnhandledExceptionFilter()
{
    HMODULE hKernel32 = LoadLibrary(TEXT("kernel32.dll"));
    if (hKernel32 ==   NULL)
        return FALSE;


    void *pOrgEntry = GetProcAddress(hKernel32, "SetUnhandledExceptionFilter");
    if (pOrgEntry == NULL)
        return FALSE;

    unsigned char newJump[ 100 ];
    UINT_PTR dwOrgEntryAddr = (UINT_PTR)pOrgEntry;
    dwOrgEntryAddr += 5; // add 5 for 5 op-codes for jmp far


    void *pNewFunc = &MyDummySetUnhandledExceptionFilter;
    UINT_PTR dwNewEntryAddr = (UINT_PTR) pNewFunc;
    DWORD dwRelativeAddr = (DWORD)(dwNewEntryAddr -  dwOrgEntryAddr);


    newJump[ 0 ] = 0xE9;  // JMP absolute
    memcpy(&newJump[ 1 ], &dwRelativeAddr, sizeof(pNewFunc));
    SIZE_T bytesWritten;
    BOOL bRet = WriteProcessMemory(GetCurrentProcess(),    pOrgEntry, newJump, sizeof(pNewFunc) + 1, &bytesWritten);
    return bRet;
}


LONG WINAPI UnhandledExceptionFilterEx(struct _EXCEPTION_POINTERS *pException)
{
    TCHAR szMbsFile[MAX_PATH] = { 0 };
    ::GetModuleFileName(NULL, szMbsFile, MAX_PATH);

    TCHAR* pFind = _tcsrchr(szMbsFile, '\\');
    if(pFind)
    {
        *(pFind+1) = 0;

        time_t minTime = 0;
        TCHAR dumpPath[MAX_PATH] = {0};

        for (int i = 0; i < s_dumpCount; ++ i)
        {
            TCHAR filename[MAX_PATH] = {0};
            sprintf_s(filename, MAX_PATH, "MiniDump_%d.dmp", i);

            *(pFind+1) = 0;
            _tcscat(szMbsFile, filename);
            if (existFile(szMbsFile))
            {
                time_t t = getmtime(szMbsFile);
                if (minTime == 0 || t < minTime)
                {
                    minTime = t;
                    _tcscpy(dumpPath, szMbsFile);
                }

                continue;
            }
            else
            {
                _tcscpy(dumpPath, szMbsFile);

                break;
            }
        }

		WinTrace("CreateMiniDump: %s", dumpPath);

        CreateMiniDump(pException, dumpPath);
    }

    if (_tcslen(s_reportPath) > 0)
    {
		WinTrace("got Exception. exec: %s", s_reportPath);

        execute(s_reportPath);
    }

    //FatalAppExit(-1,  TEXT("Fatal Error"));
    return EXCEPTION_CONTINUE_SEARCH;
}


DLLEXPORT void cd_setup(int count, LPCSTR filepath)
{
    if (count > 0)
    {
        s_dumpCount = count;
    }

    if (filepath)
    {
        _tcscpy(s_reportPath, filepath);
    }

    SetUnhandledExceptionFilter(UnhandledExceptionFilterEx);
    PreventSetUnhandledExceptionFilter();
}
